package object;

import java.util.ArrayList;

import javax.vecmath.Color3f;

import light.PointLight;

import ray.CameraRay;
import ray.Ray;
import ray_tracer.Camera;
import scene.IntersectionInfo;
import scene.Scene;

import util.Point3D;
import util.Vector3D;

public abstract class SceneObject {

	private static int MAX_DEPTH = 3;

	protected Color3f diffuseColor, specularColor;
	protected float phongAmbient, phongDiffuse, phongSpecular, phongExponent,
			reflection, transmission;
	protected String name;

	public SceneObject(String name, Color3f diffuseColor, float reflection,
			float transmission) {
		this.reflection = reflection;
		this.transmission = transmission;
		this.name = name;
		this.diffuseColor = diffuseColor;
		this.specularColor = new Color3f(1.0f, 1.0f, 1.0f);
		this.phongAmbient = 0.2f;
		this.phongExponent = 50.0f;
	}

	public abstract Point3D getClosestIntersectionPoint(Ray r);

	public abstract Vector3D getNormalAtPoint(Point3D p);

	public Color3f getSpecularColor() {
		return specularColor;
	}

	public String getName() {
		return this.name;
	}

	public Color3f getIllumination(IntersectionInfo i, int depth,
			boolean insideObject) {
		Color3f phongColor = this.getPhongColor(i);
		if (depth <= MAX_DEPTH) {
			Vector3D S = new Vector3D(i.getPoint(), Scene.getInstance()
					.getPointLights().get(0).getPosition());
			Vector3D N = i.getNormal();
			Vector3D R = this.buildReflectionRay(N, S);
			CameraRay reflectionRay = new CameraRay(i.getPoint(), new Point3D(i
					.getPoint().getX()
					+ R.getX(), i.getPoint().getY() + R.getY(), i.getPoint()
					.getZ()
					+ R.getZ()), null, false);
			float Nit = insideObject ? (0.8f / 1.0f) : (1.0f / 0.8f);
			Vector3D D = new Vector3D(Camera.EYE, i.getPoint());
			CameraRay transmissionRay = null;
			if (this.calcTransmissionDiscriminent(Nit, D, N) < 0.0f) {
				transmissionRay = reflectionRay;
			} else {
				Vector3D T = this.calcTransmissionRay(Nit, D, N);
				transmissionRay = new CameraRay(i.getPoint(), new Point3D(i
						.getPoint().getX()
						+ T.getX(), i.getPoint().getY() + T.getY(), i
						.getPoint().getZ()
						+ T.getZ()), null, !insideObject);
				if(insideObject) {
					N = N.mulScalar(-1.0f);
				}
			}
			Color3f reflectionColor = reflectionRay.getColor(depth + 1);
//			Color3f transmissionColor = new Color3f(0.0f, 0.0f, 0.0f);
//			if(insideObject) {
			Color3f transmissionColor = transmissionRay.getColor(depth + 1);
//				reflectionColor = new Color3f(0.0f, 0.0f, 0.0f);
//			}
			return new Color3f(phongColor.getX()
					+ (reflectionColor.getX() * this.reflection)
					+ (transmissionColor.getX() * this.transmission),
					phongColor.getY()
							+ (reflectionColor.getY() * this.reflection)
							+ (transmissionColor.getY() * this.transmission),
					phongColor.getZ()
							+ (reflectionColor.getZ() * this.reflection)
							+ (transmissionColor.getZ() * this.transmission));
		} else {
			return phongColor;
		}
	}

	public Color3f getPhongColor(IntersectionInfo i) {
		float redIntensity = this.getPhongIntensity(i, Scene.getInstance()
				.getPointLights(), 'R');
		float greenIntensity = this.getPhongIntensity(i, Scene.getInstance()
				.getPointLights(), 'G');
		float blueIntensity = this.getPhongIntensity(i, Scene.getInstance()
				.getPointLights(), 'B');
		return new Color3f(redIntensity, greenIntensity, blueIntensity);
	}

	private float getPhongIntensity(IntersectionInfo i,
			ArrayList<PointLight> lights, char color) {
		// Get constants
		float Ka = this.phongAmbient;
		float Kd = 1.0f, Ks = 1.0f, Ke = 1.0f, La = 1.0f;
		switch (color) {
		case 'R':
			Kd = this.diffuseColor.getX();
			Ks = this.specularColor.getX();
			Ke = this.phongExponent;
			La = Scene.getInstance().getAmbientLight().getColor().getX();
			break;
		case 'G':
			Kd = this.diffuseColor.getY();
			Ks = this.specularColor.getY();
			Ke = this.phongExponent;
			La = Scene.getInstance().getAmbientLight().getColor().getY();
			break;
		case 'B':
			Kd = this.diffuseColor.getZ();
			Ks = this.specularColor.getZ();
			Ke = this.phongExponent;
			La = Scene.getInstance().getAmbientLight().getColor().getZ();
			break;
		}
		// Ambient
		float ambientComponent = Ka * La;
		// Diffuse
		float diffuseSum = 0.0f;
		for (PointLight l : lights) {
			Vector3D S = new Vector3D(i.getPoint(), l.getPosition());
			Vector3D N = i.getNormal();
			Vector3D R = this.buildReflectionRay(N, S);
			Vector3D V = new Vector3D(i.getPoint(), Camera.EYE);
			IntersectionInfo testLightSource = Scene.getInstance()
					.getClosestIntersection(
							new Ray(i.getPoint(), l.getPosition()));
			// if(R.dotProduct(N) > 0) {
			if (testLightSource == null) {
				float Li = 0.0f;
				switch (color) {
				case 'R':
					Li = l.getColor().getX();
					break;
				case 'G':
					Li = l.getColor().getY();
					break;
				case 'B':
					Li = l.getColor().getZ();
					break;
				}
				diffuseSum += Li * (S.dotProduct(N));
			} else {
				float Li = 0.0f;
				switch (color) {
				case 'R':
					Li = l.getColor().getX();
					break;
				case 'G':
					Li = l.getColor().getY();
					break;
				case 'B':
					Li = l.getColor().getZ();
					break;
				}
				diffuseSum += Li * (S.dotProduct(N)) * Ka;
			}
			// }
		}
		diffuseSum *= Kd;
		// Specular
		float specularSum = 0.0f;
		for (PointLight l : lights) {
			Vector3D S = new Vector3D(i.getPoint(), l.getPosition());
			Vector3D N = i.getNormal();
			Vector3D R = this.buildReflectionRay(N, S);
			Vector3D V = new Vector3D(i.getPoint(), Camera.EYE);
			IntersectionInfo testLightSource = Scene.getInstance()
					.getClosestIntersection(
							new Ray(i.getPoint(), l.getPosition()));
			if (testLightSource == null) {
				float Li = 0.0f;
				switch (color) {
				case 'R':
					Li = l.getColor().getX();
					break;
				case 'G':
					Li = l.getColor().getY();
					break;
				case 'B':
					Li = l.getColor().getZ();
					break;
				}
				specularSum += Li * (float) Math.pow(R.dotProduct(V), Ke);
			}
		}
		specularSum *= Ks;
		// Result
		return ambientComponent + diffuseSum + specularSum;
	}

	private Vector3D buildReflectionRay(Vector3D N, Vector3D S) {
		return S.minus(N.mulScalar(2.0f * (S.dotProduct(N))));
	}

	private Vector3D calcTransmissionRay(float Nit, Vector3D D, Vector3D N) {
		return D.mulScalar(Nit).plus(
				N.mulScalar((float) ((Nit * (D.mulScalar(-1.0f)
						.dotProduct(N))) - Math.sqrt(this
						.calcTransmissionDiscriminent(Nit, D, N)))));
	}

	private float calcTransmissionDiscriminent(float Nit, Vector3D D, Vector3D N) {
		return (float) (1.0f + (Math.pow(Nit, 2.0f) * (Math.pow(D
				.mulScalar(-1.0f).dotProduct(N), 2.0f) - 1.0f)));
	}

}
